<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>放置箱子</title>
        <style>
            body {
                margin: 0;
                /* 隐藏body窗口区域滚动条 */
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <script type="module">
            import * as THREE from 'https://cdn.skypack.dev/three@v0.130.1'
            import { OrbitControls } from 'https://cdn.skypack.dev/three@v0.130.1/examples/jsm/controls/OrbitControls.js'
            import { GLTFLoader } from 'https://cdn.skypack.dev/three@v0.130.1/examples/jsm/loaders/GLTFLoader.js'
            import Stats from 'https://cdn.skypack.dev/three@v0.130.1/examples/jsm/libs/stats.module.js'
            import {GUI} from 'https://cdn.skypack.dev/three@v0.130.1/examples/jsm/libs/dat.gui.module.js'
            
            let controls = null, INTERSECTED = null
            let raycaster = new THREE.Raycaster();
            const mouse = new THREE.Vector2();
            const objs = []
            let plane = null
            let boxPre = null
            let boxGeo, boxMat

            let Game = {
                tickLength: 16, 				// 20Hz
                lastTick: performance.now(),	// 最后更新时间
                stopKey: 0,
                width: window.innerWidth,
                height: window.innerHeight,
                renderer: null,
                camera: null,
                scene: null,
                clock: new THREE.Clock(),
                stats: new Stats(),
                init: function(){
                    this.scene = new THREE.Scene()
                    
                    /** 相机设置  */
                    let k = this.width / this.height; //窗口宽高比
                    let s = 200; //三维场景显示范围控制系数，系数越大，显示的范围越大
                    // this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 10000);
                    this.camera = new THREE.PerspectiveCamera(60, this.width / this.height, 1, 10000)
                    this.camera.position.set( 200, 300, 700 )
                    this.camera.lookAt( 0, 0, 0 )
                   
                    this.lighting()

                    /** 创建场景对象Scene */
                    this.add()
                    
                    /** 创建渲染器对象 */
                    this.renderer = new THREE.WebGLRenderer({
                        antialias: true
                    })
                    this.renderer.setPixelRatio( window.devicePixelRatio )
                    this.renderer.setSize(this.width, this.height)
                    this.renderer.setClearColor(0xb9d3ff, 1)
                    this.renderer.setAnimationLoop(render)
                    document.body.appendChild(this.renderer.domElement)
                    document.body.appendChild(this.stats.dom)
                   
                    // 创建控件对象
                    controls = new OrbitControls(this.camera, this.renderer.domElement)
                    
                    const gui = new GUI();
                    for (let key in params) {
                        if (key.endsWith("_desc")) continue
                        gui.add(params, key ).name(params[key + "_desc"]);
                    }

                    window.onresize = function () {
                        Game.width = window.innerWidth; //窗口宽度
                        Game.height = window.innerHeight; //窗口高度
                        if (Game.renderer == null) return
                        Game.renderer.setSize(Game.width, Game.height);//设置渲染区域尺寸
                        if (Game.camera == null) return
                        k = Game.width / Game.height; //窗口宽高比
                        //修改相机对象属性
                        Game.camera.left = -s * k
                        Game.camera.right = s * k
                        Game.camera.top = s
                        Game.camera.bottom = -s
                        Game.camera.aspect = k
                        Game.camera.updateProjectionMatrix()
                    }
                    document.onkeydown = function (e) {
                        e = e || window.event;  //标准化事件对象
                        let t = e.target || e.srcElement;  //获取发生事件的元素，兼容IE和DOM
                        if (e.code == "Space") {  // 按下空格键 开始/暂停
                            // if (audio.isPlaying) {
                            //     audio.stop()
                            // } else {
                            //     audio.play()
                            // }
                            // console.log("audio: " + audio.isPlaying)
                        }
                        console.log(e.code)
                    }
                    document.onmousemove = function onMouseMove( event ) {
                        // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
                        mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
                        mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

                        // 通过摄像机和鼠标位置更新射线
                        raycaster.setFromCamera( mouse, Game.camera );

                        // 计算物体和射线的焦点
                        const intersects = raycaster.intersectObjects(objs);
                        // console.log(intersects[0] != null ? (intersects[0].point.add(intersects[0].face.normal)): '')
                        if (intersects.length > 0) {
                            INTERSECTED = intersects[0]
                            let pos = INTERSECTED.point.clone()
                            boxPre.position.copy(pos.add(INTERSECTED.face.normal).divideScalar(50).floor().multiplyScalar(50).addScalar(25))
                            // boxPre.position.multiply(new THREE.Vector3(1, 0, 1)).divideScalar(50).floor().multiplyScalar(50).addScalar(25)
                        }
                    }

                    document.onpointerdown = function onPointerDown( event ) {
                        // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
                        mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
                        mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
                        
                        // 通过摄像机和鼠标位置更新射线
                        raycaster.setFromCamera( mouse, Game.camera );

                        // 计算物体和射线的焦点
                        const intersects = raycaster.intersectObjects(objs);
                        
                        if (intersects.length > 0) {
                            INTERSECTED = intersects[0]
                            
                            if (event.button == 0) {
                                let pos = INTERSECTED.point.clone()
                                // console.log(pos.clone(), INTERSECTED.face.normal, pos.add(INTERSECTED.face.normal))
                                let box = new THREE.Mesh(boxGeo, boxMat)
                                box.position.copy(pos.add(INTERSECTED.face.normal).divideScalar(50).floor().multiplyScalar(50).addScalar(25))
                                Game.scene.add(box)
                                objs.push(box)
                                
                            }
                            
                            if (event.button == 2) {
                                if (INTERSECTED.object !== plane) {
                                    Game.scene.remove(INTERSECTED.object)
                                    objs.splice(objs.indexOf(INTERSECTED.object), 1)
                                }
                            }
                            
                            
                        }
                    }
                }, 
                lighting: function(){
                    if (this.scene == null ) return
                    /** 光源设置  */
                    // 点光源
                    let point = new THREE.PointLight(0xffffff)
                    point.position.set(400, 200, 300); //点光源位置

                    point.castShadow = true;

                    point.shadow.mapSize.width = 1024;
                    point.shadow.mapSize.height = 1024;

                    const d = 300;

                    point.shadow.camera.left = - d;
                    point.shadow.camera.right = d;
                    point.shadow.camera.top = d;
                    point.shadow.camera.bottom = - d;

                    point.shadow.camera.far = 1000;

                    //点光源添加到场景中
                    this.scene.add(point)
                    //环境光
                    this.scene.add(new THREE.AmbientLight(0x888888))
                },
                update: function(tFrame) {
                    if (tFrame === undefined) {
                        tFrame = performance.now()
                        Game.lastTick = performance.now()
                    }
                    let nextTick = Game.lastTick + Game.tickLength
                    let tickNum = 0
                    if (tFrame > nextTick) {
                        tickNum = Math.floor((tFrame - Game.lastTick) / Game.tickLength)
                    }
                    Game.lastTick = tFrame
                    this.stats.update()

                    
                    //执行渲染操作   指定场景、相机作为参数
                    Game.renderer.render(Game.scene, Game.camera);
                },
                add: function() {
                    let axisHelper = new THREE.AxesHelper(250)
                    this.scene.add(axisHelper)
                    
                    const gridHelper = new THREE.GridHelper(1000, 20)
                    this.scene.add(gridHelper)

                    plane = new THREE.Mesh(
                        new THREE.PlaneGeometry(1000, 1000),
                        new THREE.MeshBasicMaterial({
                            // visible: false, 
                            side: THREE.DoubleSide,
                            color: 0xccddcc, 
                            opacity: 0.5, 
                            transparent: true
                            })
                        )
                    plane.geometry.rotateX(- Math.PI / 2)
                    this.scene.add(plane)
                    objs.push(plane)

                    boxPre = new THREE.Mesh(
                        new THREE.BoxGeometry(50, 50, 50),
                        new THREE.MeshBasicMaterial({ color: 0xff0000, opacity: 0.5, transparent: true })
                    )
                    boxPre.position.set(25, 25, 25)
                    this.scene.add(boxPre)

                    boxGeo = new THREE.BoxGeometry(50, 50, 50)
                    boxMat = new THREE.MeshLambertMaterial( { color: 0xfeb74c, map: new THREE.TextureLoader().load( 'static/img/square-outline-textured.png' ) } );

                }
            }

            function render(tFrame) {
                Game.update()
                // Game.stopKey = requestAnimationFrame(render)
            }
            const params = {
                // enableWind: true,
                // enableWind_desc: 'Enable wind',
                // showBall: false,
                // showBall_desc: 'Show ball',
                // togglePins: togglePins
            }
            Game.init()
            // render()
            // const controls = new OrbitControls(camera, renderer.domElement);//创建控件对象
        </script>
    </body>
</html>